package com.ctakit.kiwi.common.core.bean.selectors;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.beans.PropertyDescriptor;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Selector that use the bean properties as the key and the property values as
 * the value. Because every object has a "class" property which is not required
 * in most scenario, it will be excluded from the result. If you really want it,
 * you can use the {@link #add(String...)} method to add it in the result map.
 *
 * @author lizhenmin
 */
@Slf4j
public class ClassSelector extends KeyValueMapSelector {
    private List<PropertySelector> propertySelectors = new LinkedList<PropertySelector>();
    private CompositeSelector compositeSelector = new CompositeSelector();

    /**
     * @param clazz null will cause the select methods returning an empty Map or a
     *              list of Empty map as the result.
     */
    public ClassSelector(Class<?> clazz) {
        if (null == clazz) {
            log.warn("Input class is null");
            return;
        }
        PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(clazz);
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            boolean propertyReadable = propertyDescriptor.getReadMethod() != null;
            String propertyName = propertyDescriptor.getName();
            // ignore the class property
            if ("class".equals(propertyName)) {
                continue;
            }

            if (propertyReadable) {
                PropertySelector propertySelector = new PropertySelector(propertyDescriptor.getName());
                propertySelectors.add(propertySelector);
                compositeSelector.addSubSelector(propertySelector);
            }
        }
    }

    /**
     * Exclude properties from the result map.
     */
    public ClassSelector except(String... propertyNames) {
        if (ArrayUtils.isEmpty(propertyNames)) {
            return this;
        }

        boolean updated = false;
        for (String propertyToRemove : propertyNames) {
            if (removePropertySelector(propertyToRemove)) {
                updated = true;
            }
        }

        if (updated) {
            compositeSelector = new CompositeSelector(this.propertySelectors);
        }
        return this;
    }

    private boolean removePropertySelector(String propertyName) {
        if (StringUtils.isBlank(propertyName)) {
            return false;
        }

        for (PropertySelector selector : propertySelectors) {
            if (selector.getProperty().equals(propertyName)) {
                propertySelectors.remove(selector);
                return true;
            }
        }
        return false;
    }

    /**
     * Include properties in the result map.
     */
    public ClassSelector add(String... propertyNames) {
        if (ArrayUtils.isNotEmpty(propertyNames)) {
            for (String propertyNameToAdd : propertyNames) {
                addPropertySelector(propertyNameToAdd);
            }
        }
        return this;
    }

    private void addPropertySelector(String propertyNameToAdd) {
        if (StringUtils.isBlank(propertyNameToAdd)) {
            return;
        }

        for (PropertySelector propertySelector : propertySelectors) {
            if (propertySelector.getProperty().equals(propertyNameToAdd)) {
                return;
            }
        }

        PropertySelector newPropertySelector = new PropertySelector(propertyNameToAdd);
        propertySelectors.add(newPropertySelector);
        compositeSelector.addSubSelector(newPropertySelector);
    }

    @Override
    protected Map<String, Object> doSelect(Object item) {
        return this.compositeSelector.select(item);
    }

}
